using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml;
using Serialization;

namespace Store.WP7.Framework
{
    /// <summary>
    /// Cache manager
    /// </summary>
    public class CacheManager
    {
        public static readonly DateTime NoExpirationDate = DateTime.MaxValue;
        public static readonly TimeSpan NoSlidingExpirationDelay = TimeSpan.Zero;

        readonly IsolatedStorageFile _myIsolatedStore = IsolatedStorageFile.GetUserStoreForApplication();

        private object _sync = new object();

        private static CacheManager _current;
        /// <summary>
        /// Gets the current instance of the cache
        /// </summary>
        public static CacheManager Current
        {
            get { return _current ?? (_current = new CacheManager()); }
        }

        /// <summary>
        /// Adds the specified key
        /// </summary>
        /// <param name="key">The key</param>
        /// <param name="value">The value</param>
        public void Add(string key, object value)
        {
            Add(key, value, NoExpirationDate);
        }

        /// <summary>
        /// Adds the specified key
        /// </summary>
        /// <param name="key">The key</param>
        /// <param name="value">The value</param>
        /// <param name="expirationDate">The expiration date</param>
        /// <param name="slidingDelay">The sliding expiration delay</param>
        public void Add(string key, object value, TimeSpan expirationDelay)
        {
            Add(key, value, DateTime.UtcNow + expirationDelay);
        }

        /// <summary>
        /// Adds the specified key
        /// </summary>
        /// <param name="key">The key</param>
        /// <param name="expirationDate">The expiration date</param>
        /// <param name="value">The value</param>
        public void Add(string key, object value, DateTime expirationDate)
        {
            lock (_sync)
            {
                if (_myIsolatedStore.DirectoryExists(key))
                {
                    Remove(key);
                }

                _myIsolatedStore.CreateDirectory(key);

                string fileName = string.Format("{0}\\{1}.cache", key, expirationDate.ToFileTimeUtc());
                WriteIsolatedStorage(fileName, value);
            }

        }

        /// <summary>
        /// Determines whether the cache contains the specified key
        /// </summary>
        /// <param name="key">The key</param>
        /// <returns>
        /// 	<c>true</c> if contains the specified key; otherwise, <c>false</c>.
        /// </returns>
        public bool Contains(string key)
        {
            lock (_sync)
            {
                if (_myIsolatedStore.DirectoryExists(key))
                {
                    string currentFile = GetFileNames(key).FirstOrDefault();
                    if (currentFile != null)
                    {
                        var expirationDate = GetExpirationDate(currentFile);
                        if (expirationDate >= DateTime.UtcNow)
                            return true;
                    }
                }

                Remove(key);

                return false;
            }
        }

        /// <summary>
        /// Removes the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        public void Remove(string key)
        {
            lock (_sync)
            {
                //if (!Contains(key))
                //    throw new AccessViolationException("The key does not exist in the cache");

                if (_myIsolatedStore.DirectoryExists(key))
                {
                    foreach (var currentFile in GetFileNames(key))
                    {
                        _myIsolatedStore.DeleteFile(string.Format("{0}\\{1}", key, currentFile));
                    }
                    _myIsolatedStore.DeleteDirectory(key);
                }
            }
        }

        /// <summary>
        /// Gets the file names
        /// </summary>
        /// <param name="key">The key</param>
        /// <returns></returns>
        private IEnumerable<string> GetFileNames(string key)
        {
            return _myIsolatedStore.GetFileNames(string.Format("{0}\\*.cache", key));
        }

        /// <summary>
        /// Gets the specified key.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key</param>
        /// <returns></returns>
        public T Get<T>(string key)
        {
            lock (_sync)
            {
                if (_myIsolatedStore.DirectoryExists(key))
                {
                    string currentFile = GetFileNames(key).FirstOrDefault();

                    if (currentFile != null)
                    {
                        var expirationDate = GetExpirationDate(currentFile);
                        if (expirationDate >= DateTime.UtcNow)
                            return ReadIsolatedStorage<T>(string.Format(@"{0}\{1}", key, currentFile));

                        Remove(key);
                    }
                }
                return default(T);
            }
        }

        /// <summary>
        /// Gets the expiration date from a file name
        /// </summary>
        /// <param name="filename">File name</param>
        /// <returns></returns>
        private DateTime GetExpirationDate(string filename)
        {
            var path = Path.GetFileNameWithoutExtension(filename);
            return DateTime.FromFileTimeUtc(long.Parse(path));
        }

        #region Serialization

        /// <summary>
        /// Reads from isolated storage
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private T ReadIsolatedStorage<T>(string fileName)
        {
            using (var isolatedStorageFileStream = new IsolatedStorageFileStream(fileName, FileMode.Open, _myIsolatedStore))
            {
                var value = SilverlightSerializer.Deserialize(isolatedStorageFileStream);
                return (T)value;
            }
        }

        /// <summary>
        /// Writes to isolated storage
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="value"></param>
        private void WriteIsolatedStorage(string fileName, object value)
        {
            using (var isolatedStorageFileStream = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, _myIsolatedStore))
            {
                SilverlightSerializer.Serialize(value, isolatedStorageFileStream);
            }
        }

        //private T ReadIsolatedStorage<T>(string fileName)
        //{
        //    using (var isolatedStorageFileStream = new IsolatedStorageFileStream(fileName, FileMode.Open, _myIsolatedStore))
        //    {
        //        DataContractSerializer ser = new DataContractSerializer(typeof(T));
        //        return (T)ser.ReadObject(isolatedStorageFileStream);
        //    }
        //}

        //private void WriteIsolatedStorage(string fileName, object value)
        //{
        //    using (var isolatedStorageFileStream = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, _myIsolatedStore))
        //    {
        //        DataContractSerializer ser = new DataContractSerializer(value.GetType());
        //        {
        //            System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();
        //            settings.OmitXmlDeclaration = false;
        //            settings.Indent = true;
        //            settings.NamespaceHandling = System.Xml.NamespaceHandling.OmitDuplicates;
        //            //settings.NewLineOnAttributes = true;
        //            ser.WriteObject(isolatedStorageFileStream, value);
        //        }
        //    }
        //}

        #endregion
    }
}